import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
import java.io.*;
import java.net.InetAddress;
import java.util.concurrent.*;
import java.util.*;

public class RequestPictureHandler {
	//Data members
	private ISocket _socket = null; //Socket to send messages
	private int _mss; //Picture data MSS size
	private DB _db = null; //Instance of DB
	private PictureCache _cache = null; //Cache of picture's fragments
	private ThreadPoolExecutor _executor = null; //Thread pool, handles multiple tasks
	private final static int NUM_OF_MAX_REQUESTS = 10; //Maximum tasks
	
	/**
	 * Contains data of a task for sending picture data over the network
	 */
	private class PictureTask implements Runnable{
		//Data members
		private InetAddress _address = null; //Destination IP
		private int _port; //Destination port
		private int _rcvWin; 
		private String _picName = null;
		private Date _requestedVersion = null; //Picture version that was requested
		private Date _existingVersion = null; //Current picture version in DB
		private List<Integer> _fragments = null; //Fragments to send, if empty - send whole picture
		private String _data = null;
		
		/**
		 * Constructor
		 * @param msg
		 */
		public PictureTask(String data) {
			_data = data;
		}
		
		/**
		 * Constructor
		 * @param address
		 * @param port
		 * @param from
		 * @param currVersion
		 * @param name
		 * @param rcvWin
		 * @param fragments
		 */
		public PictureTask(InetAddress address, int port, Date from, Date currVersion, 
				String name, int rcvWin, List<Integer> fragments, String data) {
			_address = address;
			_port = port;
			_requestedVersion = from;
			_picName = name;
			_rcvWin = rcvWin;
			_fragments = fragments;
			_existingVersion = currVersion;
			_data = data;
		}
		
		/**
		 * Gets the picture fragments from the hard drive
		 * @param picName - file name of the picture
		 * @return list of fragments according to MSS size
		 */
		private List<String> getFragments(String picName) {
			List<String> fragments = new LinkedList<String>();
			
			FileInputStream is = null; 
			try { //Open stream to picture file
				is = new FileInputStream(picName + ".jpg");
				
				byte[] buffer = new byte[_mss / 2]; //Hex doubles the length of data 
				int byteRead = -1;
				while ((byteRead = is.read(buffer)) != -1) { //Read all fragments
					byte[] fragment = new byte[byteRead];
					
					if (byteRead == 0)
						continue;
					
					System.arraycopy(buffer, 0, fragment, 0, byteRead);
					fragments.add(HexBin.encode(fragment));
				}
				
			} catch (FileNotFoundException ex) {
				Proj350.logger.logError("Picture file of peer " + picName + " is missing");
				return null;
			} catch (IOException ex) {
				Proj350.logger.logError("Error reading picture file of peer " + picName);
				return null;
			} finally {
				if (is == null)
					return null;
				
				try {is.close();}
				catch (IOException ex) {
					return null;
				}
			}
			
			return fragments;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof PictureTask))
				return false;
			
			PictureTask other = (PictureTask)obj;
			
			return _data.equals(other._data);
		}
		
		@Override 
		public int hashCode() {
			return _data.hashCode();
		}
		
		public void run() {	
			try {
				//Check if receiver has enough space in buffer
				if (_rcvWin < _mss)
					return;
				
				//Check if picture data already cached
				List<String> picData = _cache.getValue(_picName, _requestedVersion);
				
				//Insert picture data into cache
				if (picData == null) {
					picData = getFragments(_picName);
					
					if (picData == null) //Check if succeeded
						return;
					
					_cache.add(_picName, _requestedVersion, picData);
				}
				
				//Send selected fragments
				if (_fragments == null)
					sendEntirePicture(picData);//Send entire picture
				else 
					sendPictureByFragments(picData); //Send specified fragments 
			} catch (InterruptedException ex) {
				return;
			}
		}	
	
		/**
		 * Send all the fragments of the selected picture
		 * @param picData - fragments of the picture
		 */
		private void sendEntirePicture(List<String> picData) throws InterruptedException{
			int rcvWin = _rcvWin;
			
			//Send fragments until window is full
			for (int i = 0; i < picData.size() && rcvWin >= _mss; ++i, rcvWin -= _mss) {
				String strDate = Utils.DateToString(_existingVersion);
				String msgStr = String.format("picture($sn=%d,$n=%d)<$picversion=%s,$peername=%s,$data=%s>",i+1, picData.size(),
						strDate, _picName, picData.get(i));
 					
				_socket.send(new Message(msgStr.getBytes(), _address, _port,
						SessionsManager.getNameBySource(new Source(_socket,_address,_port))));
				
				Thread.sleep(30);
			}
		}
		
		/**
		 * Send selected fragments of the selected picture
		 * @param picData
		 */
		private void sendPictureByFragments(List<String> picData) throws InterruptedException{
			int rcvWin = _rcvWin;
			
			//Send fragments until window is full
			for (int i = 0; i < _fragments.size() && rcvWin >= _mss; ++i, rcvWin -= _mss) {
				String strDate = Utils.DateToString(_existingVersion);
				String msgStr = String.format("picture($sn=%d,$n=%d)<$picversion=%s,$peername=%s,$data=%s>",
						_fragments.get(i), picData.size(),
						strDate, _picName, picData.get(_fragments.get(i)-1));
				
				_socket.send(new Message(msgStr.getBytes(), _address, _port,
						SessionsManager.getNameBySource(new Source(_socket,_address,_port))));
				
				Thread.sleep(30);
			}
		}
	}
	
	/**
	 * Stops the handler 	
	 */
	public synchronized void stop() {
		_executor.shutdownNow();
	}
	
	/**
	 * Constructor
	 * @param socket - socket to send fragments
	 * @param MSS - picture data MSS size
	 * @param db - Instance of DB
	 */
	public RequestPictureHandler(ISocket socket, int MSS, DB db) {
		_socket = socket;
		_mss = MSS;
		_db = db;
		_cache = new PictureCache(NUM_OF_MAX_REQUESTS);
		//_executor = Executors.newFixedThreadPool(NUM_OF_MAX_REQUESTS);
		_executor = new ThreadPoolExecutor(
				NUM_OF_MAX_REQUESTS, NUM_OF_MAX_REQUESTS, 10, 
				TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
	}	

	/**
	 * Creates a request and adds it to queue
	 * @param msg - message to be handled
	 */
	public synchronized void handle(Message msg) {
		
		//Parse the message and create a task
		String[] parsed = Utils.parseMessage(new String(msg.getData()));
		
		if (parsed == null)
			return;
		
		
		Date from = Utils.StringToDate(parsed[0]);
		int rcvWin = Integer.parseInt(parsed[1]);
		
		if (parsed.length == 2) { //Check if only date 
			String data = parsed[0] + msg.getPort() + msg.getAddress().toString();
			//Check if already have current request in queue
			if (_executor.getQueue().contains(new PictureTask(data)))
				return;
			
			
			List<Record> records = _db.getPictures(from);
			
			for (Record record : records) //Create tasks from each requested picture
				_executor.execute(new PictureTask(msg.getAddress(), msg.getPort(), 
						from, (Date)record.get_picVersion().clone(), 
						record.get_peerName(), rcvWin, null, data));
			
			return;
		}
		
		if (parsed.length == 3) { //Check if only date + peerName 
			
			String data = parsed[0] + msg.getPort() + msg.getAddress().toString() + parsed[2];
			
			List<Record> records = _db.getPicture(from, parsed[2]);
			
			if (_executor.getQueue().contains(new PictureTask(data)))
				return;
			
			for (Record record : records) //Create a single task 
				_executor.execute(new PictureTask(msg.getAddress(), msg.getPort(), 
						from, record.get_picVersion(), record.get_peerName(), rcvWin, null, data));
			
			return;
		}
		
		
		String data = parsed[0] + msg.getPort() + msg.getAddress().toString() + parsed[2] + parsed[3];
		
		//Create a task that contains selected fragments
		List<Integer> fragments = new LinkedList<Integer>();
		List<Record> record = _db.getPicture(from, parsed[2]);
		if (record == null)
			return; //No such record
		
		if (parsed[3].equals("list")) {
			for (int i = 4; i < parsed.length; ++i) {
				fragments.add(new Integer(parsed[i]));
				data += parsed[i];
			}
		} else if (parsed[3].equals("range")) {
			int index = parsed[4].indexOf('-');
			if (index != -1) {
				int end = Integer.parseInt(parsed[4].substring(index+1, parsed[4].length()));
				int begin = Integer.parseInt(parsed[4].substring(0, index));
				
				int limit = end - begin + 1;
				
				for (int i = 0; i < limit; ++i) {
					data += new Integer(i + begin);
					fragments.add(new Integer(i + begin));
				}
			}
		}
		
		if (_executor.getQueue().contains(new PictureTask(data)))
			return;
		
		_executor.execute(new PictureTask(msg.getAddress(), msg.getPort(), from, 
				record.get(0).get_picVersion(), parsed[2], rcvWin, fragments, data));
	
	}

	
}
